//============================================================================
// Name        : tp1_p1.cpp
// Author      : 
// Version     :
// Copyright   : Your copyright notice
// Description : tp1 p1 compnat in C++, Ansi-style
// Usage exe.  : ./tp1_p1 teste1.txt 2 50 5000 10 7 1000 100 4 2 1 1 20 1
//============================================================================

#include <cstdlib>
#include <iostream>
#include <fstream>
#include <stdio.h>
#include <cstring>
#include "GA.h"
#include "MazeProblem.h"

using namespace std;


Point dim_solution_map,dim_labirinto,dim_reduced_labirinto;
int **Maze_problem(int **labirinto,int number_genes,int number_individuals,int number_ages,int c_rateX,int c_rateY,int m_rateX,int m_rateY,int k_tournament,int k_elitism,int max_min_type,int criterio_convergencia,CImg<int> cimg_labirinto,int aumento,int disp_refresh);

//-------------------------------------------------------------------------------------
int main(int argc, char** argv) {

	/*
	 * Prisoner's Problem
	 * */
	FILE *arquivo;
	char endereco[30];
	int number_genes,number_individuals,number_ages,c_rateX,c_rateY,m_rateX,m_rateY,
	    k_tournament,k_elitism,max_min_type,criterio_convergencia,aumento = 1,disp_refresh;
	if(argc < 2){
		cout << "Insira o labirinto: (ex: nome_do_arquivo.txt)" << endl; cin >> endereco;
		cout << "\nInsira o numero de genes: " << endl; cin >> number_genes;
		cout << "\nInsira o numero de individuos; " << endl; cin >> number_individuals;
		cout << "\nInsira o numero de geracoes: " << endl; cin >> number_ages;
		cout << "\nInsira parametro 1 da taxa de crossover: " << endl; cin >> c_rateX;
		cout << "\nInsira parametro 2 da taxa de crossover: " << endl; cin >> c_rateY;
		cout << "\nInsira parametro 1 da taxa de mutacao: " << endl; cin >> m_rateX;
		cout << "\nInsira parametro 2 da taxa de mutacao: " << endl; cin >> m_rateY;
		cout << "\nInsira parametro K do torneio: " << endl; cin >> k_tournament;
		cout << "\nInsira parametro K do elitismo: " << endl; cin >> k_elitism;
		cout << "\nInsira o tipo de problema (1- maximization e 0- minimization): " << endl; cin >> max_min_type;
		cout << "\nInsira o criterio de convergencia: " << endl; cin >> criterio_convergencia;
		cout << "\nInsira um valor para rescalar (aumentar) o mapa no display: (0 - nao aumentar)" << endl; cin >> aumento;
		if(aumento == 0) aumento = 1;
		 cout << "\nInsira a taxa de atualizacao do display: (ms)" << endl; cin >> disp_refresh;
	}
	else{
		strcpy(endereco,argv[1]);
		number_genes = atoi(argv[2]);
		number_individuals = atoi(argv[3]);
		number_ages = atoi(argv[4]);
		c_rateX = atoi(argv[5]);
		c_rateY = atoi(argv[6]);
		m_rateX = atoi(argv[7]);
		m_rateY = atoi(argv[8]);
		k_tournament = atoi(argv[9]);
		k_elitism = atoi(argv[10]);
		max_min_type = atoi(argv[11]);
		criterio_convergencia = atoi(argv[12]);
		aumento = atoi(argv[13]);
		disp_refresh = atoi(argv[14]);
	}



	arquivo = fopen(endereco,"r");
	if(arquivo == NULL){ cout << "erro ao abrir arquivo de entrada" << endl; return 0;}

	string *mapa; int n_linhas;
	mapa = load_map(arquivo,&n_linhas);

	int **solution_map,**labirinto,**reduced_labirinto;

	labirinto = str_map2bin_map(mapa,n_linhas,&dim_labirinto);
	//printMapa(labirinto,dim_labirinto); pera();
	reduced_labirinto = get_reduced_map(labirinto,dim_labirinto,&dim_reduced_labirinto);
	//printMapa(reduced_labirinto,dim_reduced_labirinto); pera();
	reduced_labirinto = preProcessing(reduced_labirinto,dim_reduced_labirinto);
	//printMapa(reduced_labirinto,dim_reduced_labirinto); pera();

	dim_solution_map = dim_reduced_labirinto;




	CImg<int> cimg_labirinto(dim_reduced_labirinto.y,dim_reduced_labirinto.x,1,3);
	Point start,end;
	cimg_forXY(cimg_labirinto,x,y){
		if(reduced_labirinto[x][y] == 1) //wall
			cimg_labirinto(y,x,0) = cimg_labirinto(y,x,1) = cimg_labirinto(y,x,2) = 0;
		else if(reduced_labirinto[x][y] == 0)//free space
			cimg_labirinto(y,x,0) = cimg_labirinto(y,x,1) = cimg_labirinto(y,x,2) = 255;
		else if(reduced_labirinto[x][y] == 5){//start
			cimg_labirinto(y,x,0) = 255;
			cimg_labirinto(y,x,1) = 0;
			cimg_labirinto(y,x,2) = 0;
			start.x = x; start.y = y;
		}
		else if(reduced_labirinto[x][y] == 3){//end
			cimg_labirinto(y,x,0) = 0;
			cimg_labirinto(y,x,1) = 255;
			cimg_labirinto(y,x,2) = 0;
			end.x = x; end.y = y;
		}
		else{
		}

	}





	solution_map = Maze_problem(reduced_labirinto,number_genes,number_individuals,number_ages,c_rateX,c_rateY,
			m_rateX,m_rateY,k_tournament,k_elitism,max_min_type,criterio_convergencia,cimg_labirinto,aumento,disp_refresh);

	//string *new_mapa; new_mapa = build_final_map(labirinto,dim_labirinto,solution_map,dim_solution_map);

	printMapa(reduced_labirinto,dim_reduced_labirinto);
	printf("\n");
	printMapa(solution_map,dim_solution_map);

	fclose(arquivo);

	//liberando memoria...
	if(mapa) delete[] mapa;

	for(int i=0;i<dim_solution_map.x;i++){
		delete[] solution_map[i];
		delete[] reduced_labirinto[i];
	}
	delete[] solution_map;
	delete[] reduced_labirinto;

	for(int i=0;i<dim_labirinto.x;i++)
		delete[] labirinto[i];
	delete[] labirinto;


	cout << "\nit works!\n";
	return 0;
}

//-------------------------------------------------------------------------------------
int **Maze_problem(int **reduced_labirinto,int number_genes,int number_individuals,int number_ages,int c_rateX,int c_rateY,int m_rateX,int m_rateY,int k_tournament,int k_elitism,int max_min_type,int criterio_convergencia,CImg<int> cimg_labirinto,int aumento,int disp_refresh){
	cout << "Prisoner's problem\n\n" << endl;

		int **solution_map,**temp_map;

		solution_map = copy_map(reduced_labirinto,dim_solution_map);
		temp_map = copy_map(reduced_labirinto,dim_solution_map);

		int free_space = get_number_of_valid_space(solution_map,dim_solution_map);
		//printf("\n%d\n",free_space); pera();

		/*
		 * starting GA part...
		 * */

		Point start = FindStartPoint(solution_map,dim_solution_map),
			  end = FindEndPoint(solution_map,dim_solution_map);


		number_genes = free_space;
		GA *prisonerProblem;
		prisonerProblem = new GA(number_ages,number_genes,number_individuals,c_rateX,c_rateY,
				                 m_rateX,m_rateY,k_tournament,k_elitism,max_min_type);
		Individual *population;
		population = new Individual[number_individuals];
		for(int i=0;i<number_individuals;i++){
			population[i].setNumGenes(number_genes);
			population[i].Initialize();
			InitializeIndividualForMazeProblem(population,i,solution_map,start,dim_solution_map);
		}


		int index_melhor;
		CImgDisplay main_disp(cimg_labirinto,"Labirinto - GA",0);
		int aumento_x = dim_solution_map.x * aumento, aumento_y = dim_solution_map.y * aumento;
		main_disp.resize(aumento_x,aumento_y);
		do{

			//prisonerProblem->printPopulation(population);
/*
			for(int i=0;i<prisonerProblem->getNumIndividuals();i++){
				clone(reduced_labirinto,temp_map,dim_solution_map);
				printf("\nIndividuo %d  #Genes: %d\n",i,population[i].getNumGenes());
				population[i].printIndividual();
				temp_map = InsertSolution(population,i,temp_map,dim_solution_map);
				printMapa(temp_map,dim_solution_map); pera();
			}
*/



			for(int i=0;i<prisonerProblem->getNumIndividuals();i++)
				population[i].setFitness(fitness_MazeProblem(population,i,number_individuals,solution_map,start,end,dim_solution_map,free_space));

			index_melhor = prisonerProblem->getHighestFitnessIndividualIndex(population);
			//index_melhor = prisonerProblem->getLowestFitnessIndividualIndex(population);
			printf("\nAge: %d",prisonerProblem->getCurrentAge());
			printf("\tFitness Average: %f",prisonerProblem->getAverageFitness(population));
			printf("\tBest Individual: %d  Fitness: %f  #Genes: %d   #Steps: %d\n",index_melhor,population[index_melhor].getFitness(),population[index_melhor].getNumGenes(),population[index_melhor].getNumGenes()/2);



			//population[index_melhor].printIndividual();
			clone(reduced_labirinto,temp_map,dim_solution_map);
			temp_map = InsertSolution(population,index_melhor,temp_map,dim_solution_map);
			//printMapa(temp_map,dim_solution_map); //pera();




			population = prisonerProblem->Run(population);

			main_disp.wait(disp_refresh);
			cimg_labirinto = update_display(temp_map,cimg_labirinto);
			cimg_labirinto.display(main_disp);


		}while((prisonerProblem->getCurrentAge() < prisonerProblem->getAge()) && !main_disp.is_closed());//&& (prisonerProblem->getAverageFitness(population) < (population[prisonerProblem->getHighestFitnessIndividualIndex(population)].getFitness()) - criterio_convergencia));

		for(int i=0;i<prisonerProblem->getNumIndividuals();i++)
			population[i].setFitness(fitness_MazeProblem(population,i,number_individuals,solution_map,start,end,dim_solution_map,free_space));


		index_melhor = prisonerProblem->getHighestFitnessIndividualIndex(population);
		//index_melhor = prisonerProblem->getLowestFitnessIndividualIndex(population);
		printf("\nGA ended\nThe Best Individual is: %d",index_melhor);
		printf("\nAge: %d",prisonerProblem->getCurrentAge());
		printf("\tFitness Average: %f",prisonerProblem->getAverageFitness(population));
		printf("\tBest Individual: %d  Fitness: %f  #Genes: %d   #Steps: %d\n",index_melhor,population[index_melhor].getFitness(),population[index_melhor].getNumGenes(),population[index_melhor].getNumGenes()/2);
		population[index_melhor].printIndividual();

		solution_map = InsertSolution(population,index_melhor,solution_map,dim_solution_map);


		for(int i=0;i<dim_solution_map.x;i++)
			delete[] temp_map[i];
		delete[] temp_map;

	while(!main_disp.is_closed())main_disp.wait();
	return solution_map;
}
//-------------------------------------------------------------------------------------
